//
//  VideoPlayerViewControllerCoordinator.swift
//  MyVideo
//
//  Created by han on 2021/4/20.
//

import UIKit
import AVKit

/// AVKit 播放器控制 AVPlayerViewController
protocol VideoPlayerViewControllerCoordinatorDelegate: class {
    func playerViewControllerCoordinator(_ coordinator: VideoPlayerViewControllerCoordinator, restoreUIForPIPStop completion: @escaping (Bool) -> Void)
    func playerViewControllerCoordinatorWillDismiss(_ coordinator: VideoPlayerViewControllerCoordinator)
}


class VideoPlayerViewControllerCoordinator: NSObject {
    init(video: Video) {
        self.video = video
        super.init()
    }
    
    deinit {
        debugHud.removeFromSuperview()
        externalDebugHud.removeFromSuperview()
    }
    
    weak var delegate: VideoPlayerViewControllerCoordinatorDelegate?
    var video: Video
    private(set) var status: Status = [] {
        didSet {
            debugHud.status = status
            externalDebugHud.status = status
            if oldValue.isBeingShown && !status.isBeingShown {
                playerViewControllerIfLoaded = nil
            }
            addDebugHUDToPlayerViewControllerIfNeeded()
        }
    }
    
    private(set) var playerViewControllerIfLoaded: AVPlayerViewController? {
        didSet {
            guard playerViewControllerIfLoaded != oldValue else { return }
            readyForDisplayObservation?.invalidate()
            readyForDisplayObservation = nil
            
            if oldValue?.delegate === self {
                oldValue?.delegate = nil
            }
            if oldValue?.hasContent(fromVideo: video) == true {
                oldValue?.player = nil
            }
            
            status = []
            
            if let playerViewController = playerViewControllerIfLoaded {
                playerViewController.delegate = self
                if !playerViewController.hasContent(fromVideo: video) {
                    let playerItem = AVPlayerItem(url: video.hlsUrl)
                    playerItem.seek(to: CMTime(seconds: video.resumeTime, preferredTimescale: 90_000), completionHandler: nil)
                    playerViewController.player = AVPlayer(playerItem: playerItem)
                }
                
                if playerViewController.isReadyForDisplay {
                    status.insert(.readyForDisplay)
                }
                
                readyForDisplayObservation = playerViewController.observe(\.isReadyForDisplay) { [weak self] observed, _ in
                    if observed.isReadyForDisplay {
                        self?.status.insert(.readyForDisplay)
                    } else {
                        self?.status.remove(.readyForDisplay)
                    }
                }
                
                debugHud.status = status
                externalDebugHud.status = status
            }
        }
    }
    
    private(set) lazy var externalDebugHud = DebugHUD()
    
    private weak var fullScreenViewController: UIViewController? // 全屏显示时的AVPlayerVC
    private var readyForDisplayObservation: NSKeyValueObservation?
    private lazy var debugHud = DebugHUD()
}

extension VideoPlayerViewControllerCoordinator {
    func presentFullScreen(from presentingViewController: UIViewController) {
        guard !status.contains(.fullScreenActive) else { return }
        removeFromParentIfNeeded()
        loadPlayerViewControllerIfNeeded()
        guard let playerViewController = playerViewControllerIfLoaded else { return }
        presentingViewController.present(playerViewController, animated: true) {
            playerViewController.player?.play()
        }
    }
    
    func embedInline(in parent: UIViewController, container: UIView) {
        loadPlayerViewControllerIfNeeded()
        guard let playerViewController = playerViewControllerIfLoaded, playerViewController.parent != parent else { return }
        removeFromParentIfNeeded()
        status.insert(.embeddedInline)
        parent.addChild(playerViewController)
        container.addSubview(playerViewController.view)
        playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            playerViewController.view.centerXAnchor.constraint(equalTo: container.centerXAnchor),
            playerViewController.view.centerYAnchor.constraint(equalTo: container.centerYAnchor),
            playerViewController.view.widthAnchor.constraint(equalTo: container.widthAnchor),
            playerViewController.view.heightAnchor.constraint(equalTo: container.heightAnchor)
        ])
        playerViewController.didMove(toParent: parent)
    }
    
    func restoreFullScreen(from presentingViewController: UIViewController, completion: @escaping () -> Void) {
        guard let playerViewController = playerViewControllerIfLoaded,
              status.contains(.pictureInPictureActive),
              !status.contains(.fullScreenActive)
        else {
            completion()
            return
        }
        playerViewController.view.translatesAutoresizingMaskIntoConstraints = true
        playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleWidth]
        presentingViewController.present(playerViewController, animated: true, completion: completion)
    }
    
    func dismiss(completion: @escaping () -> Void) {
        fullScreenViewController?.dismiss(animated: true) {
            completion()
            self.status.remove(.fullScreenActive)
        }
    }
    
    func removeFromParentIfNeeded() {
        if status.contains(.embeddedInline) {
            playerViewControllerIfLoaded?.willMove(toParent: nil)
            playerViewControllerIfLoaded?.view.removeFromSuperview()
            playerViewControllerIfLoaded?.removeFromParent()
            status.remove(.embeddedInline)
        }
    }
    
}

extension VideoPlayerViewControllerCoordinator {
    private func loadPlayerViewControllerIfNeeded() {
        if playerViewControllerIfLoaded == nil {
            playerViewControllerIfLoaded = AVPlayerViewController()
        }
    }
    
    private func addDebugHUDToPlayerViewControllerIfNeeded() {
        if status.contains(.embeddedInline) || status.contains(.fullScreenActive) {
            if let playerViewController = playerViewControllerIfLoaded,
               let contentOverlayView = playerViewController.contentOverlayView,
               !debugHud.isDescendant(of: contentOverlayView) {
                playerViewController.contentOverlayView?.addSubview(debugHud)
                debugHud.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    debugHud.centerXAnchor.constraint(equalTo: contentOverlayView.centerXAnchor),
                    debugHud.centerYAnchor.constraint(equalTo: contentOverlayView.centerYAnchor),
                    debugHud.widthAnchor.constraint(equalTo: contentOverlayView.widthAnchor),
                    debugHud.heightAnchor.constraint(equalTo: contentOverlayView.heightAnchor)
                ])
            }
        }
    }
}

extension VideoPlayerViewControllerCoordinator: AVPlayerViewControllerDelegate {
    func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
        status.insert(.pictureInPictureActive)
    }
    func playerViewController(_ playerViewController: AVPlayerViewController, failedToStartPictureInPictureWithError error: Error) {
        status.remove(.pictureInPictureActive)
    }
    func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController) {
        status.remove(.pictureInPictureActive)
    }
    func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        status.insert([.fullScreenActive, .beingPresented])
        coordinator.animate(alongsideTransition: nil) { (context) in
            self.status.remove(.beingPresented)
            if context.isCancelled {
                self.status.remove(.fullScreenActive)
            } else {
                self.fullScreenViewController = context.viewController(forKey: .to)
            }
        }
    }
    func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        status.insert([.beingDismissed])
        delegate?.playerViewControllerCoordinatorWillDismiss(self)
        
        coordinator.animate(alongsideTransition: nil) { context in
            self.status.remove(.beingDismissed)
            if !context.isCancelled {
                self.status.remove(.fullScreenActive)
            }
        }
    }
    
    func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
        if let delegate = delegate {
            delegate.playerViewControllerCoordinator(self, restoreUIForPIPStop: completionHandler)
        } else {
            completionHandler(false)
        }
    }
}

extension VideoPlayerViewControllerCoordinator {
    struct Status: OptionSet, CustomDebugStringConvertible {
        
        let rawValue: Int
        
        static let embeddedInline = Status(rawValue: 1 << 0) // 嵌入到cell
        static let fullScreenActive = Status(rawValue: 1 << 1) // 全屏
        static let beingPresented = Status(rawValue: 1 << 2) // 全屏模式，显示
        static let beingDismissed = Status(rawValue: 1 << 3) // 全屏模式，消失
        static let pictureInPictureActive = Status(rawValue: 1 << 4) // 允许iPad用户在悬浮小窗口播放视频
        static let readyForDisplay = Status(rawValue: 1 << 5)
        
        static let descriptions: [(Status, String)] = [
            (.embeddedInline, "Embedded Inline"),
            (.fullScreenActive, "Full Screen Active"),
            (.beingPresented, "Being Presented"),
            (.beingDismissed, "Being Dismissed"),
            (.pictureInPictureActive, "Picture In Picture Active"),
            (.readyForDisplay, "Ready For Display")
        ]
        
        var isBeingShown: Bool {
            return !intersection([.embeddedInline, .pictureInPictureActive, .fullScreenActive]).isEmpty
        }
        
        var debugDescription: String {
            var debugDescriptions = Status.descriptions.filter { contains($0.0) }.map { $0.1 }
            if isEmpty {
                debugDescriptions.append("Idle (Tap to full screen)")
            } else if !contains(.readyForDisplay){
                debugDescriptions.append("NOT Ready For Display")
            }
            return debugDescriptions.joined(separator: "\n")
        }
        
    }
}

private extension AVPlayerViewController {
    
    func hasContent(fromVideo video: Video) -> Bool {
        let url = (player?.currentItem?.asset as? AVURLAsset)?.url
        return url == video.hlsUrl
    }
}
